﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;

namespace Microsoft.MSAS.ManagedAPI
{
    public interface IMsasApiRequestProcessor
    {
        uint ProcessRequest(
            byte[] requestData, 
            object context, 
            MsasProcessRequestProc proc,
            MRS_ProcessRequest_Flags bloomFlags);
    }

    //// The caller implemented callback for MsasProcessRequest*().  Will get the 
    //// variant hashes in the blob from generic to specific.  By default you will get 
    //// only the interesting hashes (not ones that don't exist in the bloom) or you
    //// can pass in the MSAS_PROCESSREQUEST_FLAG_ENUMONLY flag to get everything,
    //// which in that case you can not set the bloom.  Return S_OK if you set the bloom,
    //// or S_FALSE for a cache miss.  Any other return error will cause enumeration
    //// to be terminated and that error to be return from MsasProcessRequest*().  If you
    //// pass in the MSAS_PROCESSREQUEST_FLAG_ENUMONLY flag to enumerate all hashes, the
    //// optional out params will be NULL because it is not necessary to set the them.
    //// S_OK should always be returned as S_FALSE will cause enumeration to stop.
    ////
    //HRESULT CALLBACK
    //MsasProcessRequestProc(
    //    __in_bcount(cbHash) const BYTE * pbaHash,  // SHA256 hash of URL variant
    //    __in DWORD cbHash, // always SHA256_DIGEST_LEN (32 bytes)
    //    __out_opt BOOL * pbNoMatch,  // when returning S_OK, set FALSE if setting bloom or TRUE if not
    //    __out_bcount_opt(cbChildFilter) BYTE * pbaChildFilter,  // bloom data
    //    __in DWORD cbChildFilter,  // always MSAS_CHILDFILTER_SIZE (33 bytes)
    //    __in_opt LPVOID pvContext  // caller's context data supplied to MsasProcessRequest*()
    //    );
    //typedef HRESULT ( CALLBACK * PFNMSASAPI_PROCESSREQUESTPROC )(
    //    const BYTE *,  // __in_bcount(cbHash) const BYTE * pbaHash,
    //    DWORD,  // __out_bcount(cbChildFilter) BYTE * pbaChildFilter,
    //    BOOL *,  // __out_opt BOOL * pbNoMatch,
    //    BYTE *,  // __out_bcount_opt(cbChildFilter) BYTE * pbaChildFilter,
    //    DWORD,  // __in DWORD cbChildFilter,
    //    LPVOID  // __in_opt LPVOID pvContext
    //    );
    [UnmanagedFunctionPointer(CallingConvention.Winapi, SetLastError = false, CharSet = CharSet.Unicode)]
    public delegate uint MsasProcessRequestProc(
        IntPtr bufferPtr,
        UInt32 bufferLength,
        IntPtr noMatchPtr,
        IntPtr childFilterPtr,
        UInt32 childFilterLength,
        IntPtr pvContext);

    public class MsasApiRequestProcessor : IMsasApiRequestProcessor
    {
        #region MSAS Api
        // The function for decoding the request blob from a binary byte array.
        // Data will be enumerated using callers supplied callback MsasProcessRequestProc().
        //
        //#define MSASAPI_PROCESSREQUESTDATA "MsasProcessRequestData"
        //HRESULT WINAPI
        //MsasProcessRequestData(
        //    __in_bcount(cbData) const BYTE * pbaData,  // binary request data
        //    __in SIZE_T cbData,  // size of request data
        //    __in DWORD dwFlags,  // can pass in flag to enum all and ignore the bloom data
        //    __in DWORD dwMaxVariants,  // max number of variants to process in the data block or zero for all
        //    __in PFNMSASAPI_PROCESSREQUESTPROC pFunc,  // caller supplied MsasProcessRequestProc()
        //    __in_opt LPVOID pvContext  // optional caller context data to pass to MsasProcessRequestProc()
        //    );
        //typedef HRESULT ( WINAPI * PFNMSASAPI_PROCESSREQUESTDATAW )(
        //    LPCWSTR,  // __in LPCWSTR pszData,
        //    DWORD,  // __in DWORD dwFlags,
        //    DWORD,  // __in DWORD dwMaxVariants,
        //    PFNMSASAPI_PROCESSREQUESTPROC,  // __in PFNMSASAPI_PROCESSREQUESTPROC pFunc,
        //    LPVOID  // __in_opt LPVOID pvContext
        //    );
        [DllImport("msasapi.dll", SetLastError = false, CharSet = CharSet.Unicode)]
        public static extern uint MsasProcessRequestData(
            byte[] cbData,
            UInt32 cbDataLength,  // this looks wrong, SIZE_T is arch dependent (probably should be UIntPtr)
            MRS_ProcessRequest_Flags dwCanonicalFlags,
            UInt32 dwMaxVariants,
            MsasProcessRequestProc pFunc,
            IntPtr pvContext);

        #endregion // MSAS Api

        #region IMsasApiRequestProcessor Members

        uint IMsasApiRequestProcessor.ProcessRequest(
            byte[] requestData, 
            object context, 
            MsasProcessRequestProc proc, 
            MRS_ProcessRequest_Flags bloomFlags)
        {
            uint result = MRS_HRESULTS.S_OK;
            GCHandle pvContext = new GCHandle();

            try
            {
                pvContext = GCHandle.Alloc(context);
                // This is the p/invoke call.
                result = MsasProcessRequestData(requestData, (UInt32)requestData.Length, bloomFlags, 0, proc, GCHandle.ToIntPtr(pvContext));
            }
            finally
            {
                if (pvContext.IsAllocated)
                {
                    pvContext.Free();
                }
            }

            return result;
        }
        #endregion
    }
}
